/* eslint-disable no-return-await */
/* eslint-disable no-underscore-dangle */
import { useMemoizedFn, useSetState } from 'ahooks';
import React, { useEffect, useRef, useState } from 'react';
import { ITerminalOptions, Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import {
  XTerminal,
  TerminalApi,
  XtermTerminalProps,
  TextConfig,
} from './types';

import { ColorEnum, getTextWithColor } from './tools';

export function nextTick(fun: () => void) {
  Promise.resolve().then(fun);
}

const terminalTitleTemplate = '$: ';
const defaultPreLog = '\r\n*** COMMAND START ***\r\n';

const defaultOptions: (disableStdin?: boolean) => ITerminalOptions = (
  disableStdin?: boolean,
) => ({
  rendererType: 'canvas', // 渲染类型
  theme: {
    foreground: '#333', // 字体
    background: '#fff', // 背景色
    cursor: '#333',
    selection: '#333',
  },
  disableStdin,
  cursorWidth: disableStdin ? undefined : 5,
  fontSize: 12,
  cursorBlink: !disableStdin, // 光标闪烁
  cursorStyle: 'bar', // 光标样式 'block' | 'underline' | 'bar'
  scrollback: 100, // 当行的滚动超过初始值时保留的行视窗，越大回滚能看的内容越多，
  screenReaderMode: true,
});

const XtermTerminal: React.FC<XtermTerminalProps> = ({
  className,
  style,
  height = 0,
  width,
  onCommand,
  onClear,
  onCopy,
  onPaste,
  disableStdin,
  preLog = defaultPreLog,
  command,
  onLoad,
  lineTitle = terminalTitleTemplate,
  colorRules,
  options,
}) => {
  const terminalWrapperRef = useRef<HTMLDivElement>(null);
  // 终端中输入的缓存
  const terminalCmdRef = useRef<{
    // 定义变量获取整行数据
    currentLineData: string;
    // 历史行输入数据
    historyLineData: string[];
    last: number;
  }>({
    currentLineData: '',
    historyLineData: [],
    last: 0,
  });
  const [wrapperStyle, setWrapperStyle] = useState<React.CSSProperties>({});
  const [state, setState] = useSetState<{
    term?: XTerminal;
    fitAddon?: any;
  }>({
    fitAddon: undefined,
    term: undefined,
  });

  // api中使用的
  const writeln = useMemoizedFn((label?: string | TextConfig) => {
    const { term } = state;
    term?.write('\r\n');
    term?.write(lineTitle);
    if (!label || typeof label === 'string') {
      term?.write(getTextWithColor(label as string, colorRules));
      terminalCmdRef.current.currentLineData = (label || '') as string;
    } else {
      const { text, color } = label;
      term?.write(`${ColorEnum[color]}${text}${ColorEnum.RESET}`);
      terminalCmdRef.current.currentLineData = text || '';
    }
  });
  // api中使用
  const write = useMemoizedFn((label?: string | TextConfig) => {
    const { term } = state;
    if (!label || typeof label === 'string') {
      if (((label as string) || '').includes('\n')) {
        term?.write((label || '') as string);
        term?.write(lineTitle);
        terminalCmdRef.current.currentLineData = '';
      } else {
        term?.write(getTextWithColor(label as string, colorRules));
        terminalCmdRef.current.currentLineData += (label || '') as string;
      }
    } else {
      const { text, color } = label;
      if (((text as string) || '').includes('\n')) {
        term?.write((text || '') as string);
        term?.write(lineTitle);
        terminalCmdRef.current.currentLineData = '';
      } else {
        term?.write(`${ColorEnum[color]}${text}${ColorEnum.RESET}`);
        terminalCmdRef.current.currentLineData += text || '';
      }
    }
  });

  // 清理日志
  const clearLog = useMemoizedFn(() => {
    const { term } = state;
    let cols = terminalCmdRef.current.currentLineData.length + lineTitle.length;
    while (cols > 0) {
      term?.write('\b \b');
      cols -= 1;
    }
    term?.clear();
    term?.writeln(preLog);
    term?.write(lineTitle);
    if (command) {
      terminalCmdRef.current.currentLineData = command || '';
      term?.writeln(command || '');
    } else {
      terminalCmdRef.current.currentLineData = '';
    }
    onClear && onClear();
  });

  // 重置终端尺寸
  const resizeTerm = useMemoizedFn(() => {
    const { fitAddon, term } = state;
    try {
      fitAddon?.fit();
      term?.scrollToBottom();
    } catch (e) {
      console.log(e);
    }
  });

  // 终端组件api
  const terminalApi: TerminalApi = {
    clearLog,
    writeln,
    write,
    resize: resizeTerm,
    getCommand: useMemoizedFn(() => terminalCmdRef.current.currentLineData),
  };

  // 终端输入的字符接收
  const onTermDataChange = useMemoizedFn((key) => {
    const term = state.term as XTerminal;
    if (key === '\u001b[A') {
      // up键的时候
      let len = 0;
      if (terminalCmdRef.current.historyLineData.length > 0) {
        len = terminalCmdRef.current.historyLineData.length + 1;
      }
      if (
        terminalCmdRef.current.last < len &&
        terminalCmdRef.current.last > 0
      ) {
        // 当前行有数据的时候进行删除掉在进行渲染上存储的历史数据
        for (
          let i = 0;
          i < terminalCmdRef.current.currentLineData.length;
          i++
        ) {
          if (term._core.buffer.x > lineTitle.length + 1) {
            term.write('\b \b');
          }
        }
        const text =
          terminalCmdRef.current.historyLineData[
            terminalCmdRef.current.last - 1
          ];
        term.write(text);
        // 重点，一定要记住存储当前行命令保证下次up或down时不会光标错乱覆盖终端提示符
        terminalCmdRef.current.currentLineData = text;

        terminalCmdRef.current.last--;
      }
    } else if (key === '\u001b[B') {
      // down键
      let lent = 0;
      if (terminalCmdRef.current.historyLineData.length > 0) {
        lent = terminalCmdRef.current.historyLineData.length - 1;
      }
      if (
        terminalCmdRef.current.last < lent &&
        terminalCmdRef.current.last > -1
      ) {
        for (
          let i = 0;
          i < terminalCmdRef.current.currentLineData.length;
          i++
        ) {
          if (term._core.buffer.x > lineTitle.length + 1) {
            term.write('\b \b');
          }
        }
        const text =
          terminalCmdRef.current.historyLineData[
            terminalCmdRef.current.last + 1
          ];
        term.write(text);
        terminalCmdRef.current.currentLineData = text;
        terminalCmdRef.current.last++;
      }
    } else if (key.charCodeAt(0) === 13) {
      // enter键
      // 将行数据进行添加进去
      if (terminalCmdRef.current.currentLineData !== '') {
        // 将当前行数据传入历史命令数组中存储
        terminalCmdRef.current.historyLineData.push(
          terminalCmdRef.current.currentLineData,
        );
        // 定义当前行命令在整个数组中的位置
        terminalCmdRef.current.last =
          terminalCmdRef.current.historyLineData.length;
      }
      // 当输入clear时清空终端内容
      if (terminalCmdRef.current.currentLineData === 'clear') {
        clearLog();
      } else if (terminalCmdRef.current.currentLineData !== '') {
        onCommand && onCommand(terminalCmdRef.current.currentLineData);
      } else {
        term.write(`\r\n${lineTitle}`);
      }
      // 在这可以进行发起请求将整行数据传入
      // 清空当前行数据
      terminalCmdRef.current.currentLineData = '';
    } else if (key.charCodeAt(0) === 127) {
      // 删除键--》当前行偏移量x大于终端提示符所占位置时进行删除
      if (term._core.buffer.x > lineTitle.length) {
        terminalCmdRef.current.currentLineData =
          terminalCmdRef.current.currentLineData.slice(0, -1);
        term.write('\b \b');
      }
    } else {
      // 啥也不做的时候就直接输入
      terminalCmdRef.current.currentLineData += key;
      term.write(key);
    }
  });

  /**
   *  自定义键盘输入事件
   * 解决问题：
   * 1、禁用光标左右移动，以免造成命令错乱
   * 2、重写复制粘贴功能
   */
  const customKeyEventHandler = useMemoizedFn((event: KeyboardEvent) => {
    const { type, ctrlKey, metaKey, keyCode } = event;
    // 禁用左右移动 <- 37 ->39
    if (keyCode === 37 || keyCode === 39) {
      return false;
    }
    // v 86 c 67
    if (type === 'keydown') {
      // 复制
      const ctrlC = ctrlKey && keyCode === 67;
      const metaC = metaKey && keyCode === 67;
      if (ctrlC || metaC) {
        const selection = state.term?.getSelection();
        if (selection && onCopy) {
          (async () => await onCopy(selection))();
        }
        return false;
      }
      // 粘贴
      const ctrlV = ctrlKey && keyCode === 86;
      const metaV = metaKey && keyCode === 86;
      if (ctrlV || metaV) {
        if (onPaste) {
          (async () => {
            const text = await onPaste();
            terminalCmdRef.current.currentLineData += text || '';
            state.term?.write(text || '');
          })();
        }
        return false;
      }
    }
    return true;
  });
  // 键盘事件响应注册
  const registerKeyActions = (term: XTerminal) => {
    // 使其能够输入汉字
    term.onData(onTermDataChange);
    term.attachCustomKeyEventHandler(customKeyEventHandler);
  };

  const initTerminal = () => {
    const element = terminalWrapperRef.current;
    if (!element) return;
    // 设置了cols或者fitAddon.fit(); 当一行字符超过80个过会替换现在的内容，否则换行
    const term = new Terminal({
      ...defaultOptions(disableStdin),
      ...(options || {}),
    });
    const fitAddon = new FitAddon();
    term.loadAddon(fitAddon);
    term.open(element);
    // 自适应大小(使终端的尺寸和几何尺寸适合于终端容器的尺寸)，初始化的时候宽高都是对的
    term.focus();
    if (preLog) term.write(`${preLog}\r\n`);
    const nterm = term as XTerminal;
    registerKeyActions(nterm);
    if (lineTitle) term.write(`${lineTitle}`);
    if (command) {
      terminalCmdRef.current.currentLineData = command || '';
      term.writeln(command || '');
    }
    onLoad && onLoad(nterm, terminalApi);
    setState({ term: nterm, fitAddon });
    nextTick(resizeTerm);
  };

  // 初始化样式
  useEffect(() => {
    setWrapperStyle({ ...(style || {}), height, width });
  }, [height, width, style]);

  useEffect(() => {
    resizeTerm();
  }, [height, width]);

  // 初始化终端
  useEffect(() => {
    initTerminal();
    return () => {
      state.term?.dispose();
    };
  }, []);

  return (
    <div
      className={`xterm-terminal-wrapper ${className || ''}`}
      style={wrapperStyle}
      ref={terminalWrapperRef}
    />
  );
};

export default React.memo(XtermTerminal);
